﻿//
// File: FolderScanner.cs
//
// Purpose:  implements folderscanner class, which operates in windows api level.
// Author: MotherHacker @ cachecleaner.codeplex.com


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CacheCleaner
{
        /// <summary>
        /// Search options for folder scanner.
        /// </summary>
        public enum SearchOptions
        { 
            OnlyFiles,
            OnlyDirectories,
         
            Shallow,
            Recursive,

            Default = (OnlyFiles | OnlyDirectories | Recursive),

        }

        /// <summary>
        /// Fast folder scanner (uses windows kernel api calls)
        /// </summary>
        /// <remarks>  
        /// FolderScanner scanner = new FolderScanner(@"C:\", "*.exe", SearchOptions.OnlyFiles);
        /// foreach (var match in scanner)
        /// {
        ///     System.Console.WriteLine(match);
        /// }
        /// System.Console.ReadLine();
        /// 
        ///</remarks>
        public class FolderScanner : IEnumerable<System.IO.FileSystemInfo>, IDisposable
        {
            private string searchPattern;
            private string initialPath = null;
            private SearchOptions searchOptions = SearchOptions.Default;


            /// <summary>
            /// Search pattern for files and directories.
            /// </summary>
            public string SearchPattern
            {
                get { return searchPattern; }
                set { searchPattern = value; }
            }
           
            /// <summary>
            /// Initial Path
            /// </summary>
            public string InitialPath
            {
                get { return initialPath; }
                set { initialPath = value; }
            }

            /// <summary>
            /// Search options for the folder traversal.
            /// </summary>
            public SearchOptions SearchOptions
            {
                get { return searchOptions; }
                set { searchOptions = value; }
            }

            /// <summary>
            /// If Unauthorized Access or exception occurs during search, setting this to false will silently continue traversal.
            /// </summary>
            public bool ThrowIOException
            {
                get;
                set;
            }

            public FolderScanner(string searchPath, string searchPattern, SearchOptions searchOptions)
            {
                this.initialPath = searchPath;
                this.searchPattern = searchPattern;
                this.searchOptions = searchOptions;

                this.ThrowIOException = false;
            }

            #region IEnumerable<FileSystemInfo> Members

            public IEnumerator<System.IO.FileSystemInfo> GetEnumerator()
            {
                FileDirectoryEnumerator e = new FileDirectoryEnumerator();
                e.SearchForDirectory = (this.searchOptions & SearchOptions.OnlyDirectories) == SearchOptions.OnlyDirectories;
                e.SearchForFile = (this.searchOptions & SearchOptions.OnlyFiles) == SearchOptions.OnlyFiles;
                e.SearchPath = this.initialPath;
                e.SearchPattern = this.searchPattern;
                e.ThrowIOException = this.ThrowIOException;
                myList.Add(e);
                return e;
            }
            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion

            /// <summary>
            /// Dispose
            /// </summary>
            public void Dispose()
            {
                foreach (FileDirectoryEnumerator e in myList)
                {
                    e.Dispose();
                }
                myList.Clear();
            }

            private List<FileDirectoryEnumerator> myList = new List<FileDirectoryEnumerator>();

            #region FileDirectoryEnumerator class
            internal class FileDirectoryEnumerator : IEnumerator<System.IO.FileSystemInfo>, IDisposable
            {
                /// <summary>
                /// Current result object.
                /// </summary>
                private System.IO.FileSystemInfo objCurrentObject = null;

                private bool bolIsEmpty = false;
                /// <summary>
                /// Is structure empty?
                /// </summary>
                public bool IsEmpty
                {
                    get { return bolIsEmpty; }
                }
                private int intSearchedCount = 0;
                /// <summary>
                /// Numbers of records searched.
                /// </summary>
                public int SearchedCount
                {
                    get { return intSearchedCount; }
                }
                private bool bolIsFile = true;
                /// <summary>
                /// Is the record File? true for File; false for Directory, Symbolic link, etc.
                /// </summary>
                public bool IsFile
                {
                    get { return bolIsFile; }
                }
                private int intLastErrorCode = 0;
                /// <summary>
                /// Returns the last error code.
                /// </summary>
                public int LastErrorCode
                {
                    get { return intLastErrorCode; }
                }
                /// <summary>
                /// Returns the Filename/Directoryname
                /// </summary>
                public string Name
                {
                    get
                    {
                        if (this.objCurrentObject != null)
                        {
                            return objCurrentObject.Name;
                        }
                        return null;
                    }
                }
                /// <summary>
                /// File attributes
                /// </summary>
                public System.IO.FileAttributes Attributes
                {
                    get { return (System.IO.FileAttributes)myData.dwFileAttributes; }
                }
                /// <summary>
                /// Creation time
                /// </summary>
                public System.DateTime CreationTime
                {
                    get
                    {
                        long time = ToLong(myData.ftCreationTime_dwHighDateTime, myData.ftCreationTime_dwLowDateTime);
                        System.DateTime dtm = System.DateTime.FromFileTimeUtc(time);
                        return dtm.ToLocalTime();
                    }
                }
                /// <summary>
                /// Access time
                /// </summary>
                public System.DateTime LastAccessTime
                {
                    get
                    {
                        long time = ToLong(myData.ftLastAccessTime_dwHighDateTime, myData.ftLastAccessTime_dwLowDateTime);
                        System.DateTime dtm = System.DateTime.FromFileTimeUtc(time);
                        return dtm.ToLocalTime();
                    }
                }
                /// <summary>
                /// Modified time
                /// </summary>
                public System.DateTime LastWriteTime
                {
                    get
                    {
                        long time = ToLong(myData.ftLastWriteTime_dwHighDateTime, myData.ftLastWriteTime_dwLowDateTime);
                        System.DateTime dtm = System.DateTime.FromFileTimeUtc(time);
                        return dtm.ToLocalTime();
                    }
                }
                /// <summary>
                /// File size
                /// </summary>
                public long FileLength
                {
                    get
                    {
                        if (this.bolIsFile)
                            return ToLong(myData.nFileSizeHigh, myData.nFileSizeLow);
                        else
                            return 0;
                    }
                }


                #region Getters and Setters

                private bool bolThrowIOException = false;
                /// <summary>
                /// Should IOException be thrown when error is encountered.
                /// </summary>
                public bool ThrowIOException
                {
                    get { return this.bolThrowIOException; }
                    set { this.bolThrowIOException = value; }
                }
                private bool bolReturnStringType = true;
                /// <summary>
                /// Should the scanner return file path or FileSystemInfo record.
                /// </summary>
                public bool ReturnStringType
                {
                    get { return bolReturnStringType; }
                    set { bolReturnStringType = value; }
                }

                private string strSearchPattern = "*";
                /// <summary>
                /// Search pattern use "*" for global
                /// </summary>
                public string SearchPattern
                {
                    get { return strSearchPattern; }
                    set { strSearchPattern = value; }
                }
                private string strSearchPath = null;
                /// <summary>
                /// Initial folder path for search.
                /// </summary>
                public string SearchPath
                {
                    get { return strSearchPath; }
                    set { strSearchPath = value; }
                }

                private bool bolSearchForFile = true;
                /// <summary>
                /// Include files in results
                /// </summary>
                public bool SearchForFile
                {
                    get { return bolSearchForFile; }
                    set { bolSearchForFile = value; }
                }
                private bool bolSearchForDirectory = true;
                /// <summary>
                /// Include directories in results
                /// </summary>
                public bool SearchForDirectory
                {
                    get { return bolSearchForDirectory; }
                    set { bolSearchForDirectory = value; }
                }


                #endregion

                #region IDisposable 
                /// <summary>
                /// Disposes resources / closes the search handle.
                /// </summary>
                public void Dispose()
                {
                    this.CloseHandler();
                }
                #endregion            

                #region IEnumerator FileSystemInfo

                /// <summary>
                /// Current record
                /// </summary>
                public System.IO.FileSystemInfo Current
                {
                    get { return objCurrentObject; }
                }
                /// <summary>
                /// Move to next.
                /// </summary>
                /// <returns>true if available, false otherwise.</returns>
                public bool MoveNext()
                {
                    bool success = false;
                    while (true)
                    {
                        if (this.bolStartSearchFlag)
                            success = this.SearchNext();
                        else
                            success = this.StartSearch();
                        if (success)
                        {
                            if (this.UpdateCurrentObject())
                                return true;
                        }
                        else
                        {
                            this.objCurrentObject = null;
                            return false;
                        }
                    }
                }

                /// <summary>
                /// reset
                /// </summary>
                public void Reset()
                {
                    if (this.strSearchPath == null)
                        throw new System.ArgumentNullException("SearchPath can not null");
                    if (this.strSearchPattern == null || this.strSearchPattern.Length == 0)
                        this.strSearchPattern = "*";

                    this.intSearchedCount = 0;
                    this.objCurrentObject = null;
                    this.CloseHandler();
                    this.bolStartSearchFlag = false;
                    this.bolIsEmpty = false;
                    this.intLastErrorCode = 0;
                }

                #endregion

                #region IEnumerator Members

                object System.Collections.IEnumerator.Current
                {
                    get { return objCurrentObject; }
                }

                #endregion

                #region Win32 API Structures and Methods

                [Serializable,
                    System.Runtime.InteropServices.StructLayout
                        (System.Runtime.InteropServices.LayoutKind.Sequential,
                        CharSet = System.Runtime.InteropServices.CharSet.Auto
                        ),
                    System.Runtime.InteropServices.BestFitMapping(false)]
                private struct WIN32_FIND_DATA
                {
                    public int dwFileAttributes;
                    public int ftCreationTime_dwLowDateTime;
                    public int ftCreationTime_dwHighDateTime;
                    public int ftLastAccessTime_dwLowDateTime;
                    public int ftLastAccessTime_dwHighDateTime;
                    public int ftLastWriteTime_dwLowDateTime;
                    public int ftLastWriteTime_dwHighDateTime;
                    public int nFileSizeHigh;
                    public int nFileSizeLow;
                    public int dwReserved0;
                    public int dwReserved1;
                    [System.Runtime.InteropServices.MarshalAs
                         (System.Runtime.InteropServices.UnmanagedType.ByValTStr,
                         SizeConst = 260)]
                    public string cFileName;
                    [System.Runtime.InteropServices.MarshalAs
                         (System.Runtime.InteropServices.UnmanagedType.ByValTStr,
                         SizeConst = 14)]
                    public string cAlternateFileName;
                }

                [System.Runtime.InteropServices.DllImport
                     ("kernel32.dll",
                     CharSet = System.Runtime.InteropServices.CharSet.Auto,
                     SetLastError = true)]
                private static extern IntPtr FindFirstFile(string pFileName, ref WIN32_FIND_DATA pFindFileData);

                [System.Runtime.InteropServices.DllImport
                     ("kernel32.dll",
                     CharSet = System.Runtime.InteropServices.CharSet.Auto,
                     SetLastError = true)]
                private static extern bool FindNextFile(IntPtr hndFindFile, ref WIN32_FIND_DATA lpFindFileData);

                [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
                private static extern bool FindClose(IntPtr hndFindFile);

                private static long ToLong(int hi, int lo)
                {
                    long v = (uint)hi;
                    v = v << 0x20;
                    v = v | ((uint)lo);
                    return v;
                }

                private static void WinIOError(int errorCode, string str)
                {
                    switch (errorCode)
                    {
                        case 80:
                            throw new System.IO.IOException("IO_FileExists :" + str);
                        case 0x57:
                            throw new System.IO.IOException("IOError:" + MakeHRFromErrorCode(errorCode));
                        case 0xce:
                            throw new System.IO.PathTooLongException("PathTooLong:" + str);
                        case 2:
                            throw new System.IO.FileNotFoundException("FileNotFound:" + str);
                        case 3:
                            throw new System.IO.DirectoryNotFoundException("PathNotFound:" + str);
                        case 5:
                            throw new UnauthorizedAccessException("UnauthorizedAccess:" + str);
                        case 0x20:
                            throw new System.IO.IOException("IO_SharingViolation:" + str);
                    }
                    throw new System.IO.IOException("IOError:" + MakeHRFromErrorCode(errorCode));
                }

                private static int MakeHRFromErrorCode(int errorCode)
                {
                    return (-2147024896 | errorCode);
                }

                #endregion

                #region Constants and Wrappers

                private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
                /// <summary>
                /// Search handle
                /// </summary>
                private System.IntPtr intSearchHandler = INVALID_HANDLE_VALUE;

                private WIN32_FIND_DATA myData = new WIN32_FIND_DATA();
                private bool bolStartSearchFlag = false;

                private void CloseHandler()
                {
                    if (this.intSearchHandler != INVALID_HANDLE_VALUE)
                    {
                        FindClose(this.intSearchHandler);
                        this.intSearchHandler = INVALID_HANDLE_VALUE;
                    }
                }

                private bool StartSearch()
                {
                    bolStartSearchFlag = true;
                    bolIsEmpty = false;
                    objCurrentObject = null;
                    intLastErrorCode = 0;

                    string strPath = System.IO.Path.Combine(strSearchPath, this.strSearchPattern);
                    this.CloseHandler();
                    intSearchHandler = FindFirstFile(strPath, ref myData);
                    if (intSearchHandler == INVALID_HANDLE_VALUE)
                    {
                        intLastErrorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                        if (intLastErrorCode == 2)
                        {
                            bolIsEmpty = true;
                            return false;
                        }
                        if (this.bolThrowIOException)
                            WinIOError(intLastErrorCode, strSearchPath);
                        else
                            return false;
                    }
                    return true;
                }

                private bool SearchNext()
                {
                    if (bolStartSearchFlag == false)
                        return false;
                    if (bolIsEmpty)
                        return false;
                    if (intSearchHandler == INVALID_HANDLE_VALUE)
                        return false;
                    intLastErrorCode = 0;
                    if (FindNextFile(intSearchHandler, ref myData) == false)
                    {
                        intLastErrorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                        if (intLastErrorCode != 0 && intLastErrorCode != 0x12)
                        {
                           // this.CloseHandler();

                            if (this.bolThrowIOException)
                                WinIOError(intLastErrorCode, strSearchPath);
                            else
                                return true;
                        }
                        return false;
                    }
                    return true;
                }

                private bool UpdateCurrentObject()
                {
                    if (intSearchHandler == INVALID_HANDLE_VALUE)
                        return false;
                    bool Result = false;
                    this.objCurrentObject = null;
                    if ((myData.dwFileAttributes & 0x10) == 0)
                    {
                        this.bolIsFile = true;
                        if (this.bolSearchForFile)
                            Result = true;
                    }
                    else
                    {
                        this.bolIsFile = false;
                        if (this.bolSearchForDirectory)
                        {
                            if (myData.cFileName == "." || myData.cFileName == "..")
                                Result = false;
                            else
                                Result = true;
                        }
                    }
                    if (Result)
                    {
                        string p = System.IO.Path.Combine(this.strSearchPath, myData.cFileName);
                        if (this.bolIsFile)
                        {
                            this.objCurrentObject = new System.IO.FileInfo(p);
                        }
                        else
                        {
                            this.objCurrentObject = new System.IO.DirectoryInfo(p);
                        }
                        this.intSearchedCount++;
                    }
                    return Result;
                }
                #endregion

            }



       #endregion
    }
}
